#pragma once

#include <source_location>
#include <string_view>
#include <vector>
#include <chrono>
#include <format>

namespace sl {
namespace loger {
    enum class Level {
        LOG_DEBUG = 0,
        LOG_INFO,
        LOG_WARN,
        LOG_ERROR
    };

    namespace __detail {
        template <typename... Args>
        std::string format(std::string_view fmt, Args&&... args)
        {
            return std::vformat(fmt, std::make_format_args(args...));
        }

        void init();
        template <Level level>
        void loger(const std::string_view str);
        template <>
        void loger<Level::LOG_DEBUG>(const std::string_view str);
        template <>
        void loger<Level::LOG_INFO>(const std::string_view str);
        template <>
        void loger<Level::LOG_WARN>(const std::string_view str);
        template <>
        void loger<Level::LOG_ERROR>(const std::string_view str);
    } // namespace __detail

    struct Fmt final {
        Fmt(const char* fmt, const std::source_location& location =
                                 std::source_location::current()):
            _fmt(fmt), _location(location)
        { }
        Fmt(const std::string fmt, const std::source_location& location =
                                       std::source_location::current()):
            _fmt(fmt), _location(location)
        { }
        Fmt(const std::string_view fmt, const std::source_location& location =
                                            std::source_location::current()):
            _fmt(fmt), _location(location)
        { }
        std::string_view           _fmt;
        const std::source_location _location;
    };

    void  setLevel(Level level);
    Level getLevel();

    void debug(Fmt fmt, const auto&... args)
    {
        if(getLevel() > Level::LOG_DEBUG) {
            return;
        }
        auto filename = [](std::string_view str) -> std::string_view {
            size_t pos = str.find_last_of('/');
            if(pos == std::string_view::npos)
                return str;
            return str.substr(pos + 1);
        };
        std::string prefix = __detail::format("[in {}:{}] ",
            filename(fmt._location.file_name()), fmt._location.line());
        prefix.append(
            __detail::format(fmt._fmt, std::forward<decltype(args)>(args)...));

        __detail::loger<Level::LOG_DEBUG>(prefix);
    }

    template <typename... Args>
    void info(Fmt fmt, Args&&... args)
    {
        if(getLevel() > Level::LOG_INFO) {
            return;
        }
        std::string prefix =
            __detail::format(fmt._fmt, std::forward<Args>(args)...);
        __detail::loger<Level::LOG_INFO>(prefix);
    }

    template <typename... Args>
    void warn(Fmt fmt, Args&&... args)
    {
        if(getLevel() > Level::LOG_WARN) {
            return;
        }
        std::string prefix =
            __detail::format(fmt._fmt, std::forward<Args>(args)...);
        __detail::loger<Level::LOG_WARN>(prefix);
    }

    template <typename... Args>
    void error(Fmt fmt, Args&&... args)
    {
        auto filename = [](std::string_view str) -> std::string_view {
            size_t pos = str.find_last_of('/');
            if(pos == std::string_view::npos)
                return str;
            return str.substr(pos + 1);
        };
        std::string prefix = __detail::format("[in {}:{}] ",
            filename(fmt._location.file_name()), fmt._location.line());
        prefix.append(
            __detail::format(fmt._fmt, std::forward<decltype(args)>(args)...));

        __detail::loger<Level::LOG_ERROR>(prefix);
    }

} // namespace loger

} // namespace sl

#define log_debug sl::loger::debug
#define log_info sl::loger::info
#define log_warn sl::loger::warn
#define log_error sl::loger::error
